package cn.mengxw.evcard;

import cn.mengxw.evcard.config.ConfigManage;
import cn.mengxw.evcard.entity.ForbiddenWordBank;
import cn.mengxw.evcard.entity.KeywordReply;
import cn.mengxw.evcard.entity.cmd.BaseCommand;
import cn.mengxw.evcard.entity.cmd.Command;
import cn.mengxw.evcard.entity.cmd.CommandFacatory;
import com.google.gson.Gson;
import com.hankcs.hanlp.suggest.Suggester;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.meowy.cqp.jcq.entity.ICQVer;
import org.meowy.cqp.jcq.entity.IMsg;
import org.meowy.cqp.jcq.entity.IRequest;
import org.meowy.cqp.jcq.event.IType;
import org.meowy.cqp.jcq.event.JcqAppAbstract;

import javax.swing.*;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
public class EvcardGroup extends JcqAppAbstract implements ICQVer {

  /**
   * 返回应用的ApiVer、Appid，打包后将不会调用<br>
   * 本函数【禁止】处理其他任何代码，以免发生异常情况。<br>
   * 如需执行初始化代码请在 startup 事件中执行（Type=1001）。<br>
   *
   * @return 应用信息
   */
  @Override
  public String appInfo() {
    String AppID = "cn.mengxw.evcard"; // 记住编译后的文件和json也要使用appid做文件名
    return CQAPIVER + "," + AppID;
  }

  /**
   * 酷Q启动 (Type={@link IType#EVENT_Startup 1001})<br>
   * 本方法会在酷Q【主线程】中被调用。<br>
   * 请在这里执行插件初始化代码。<br>
   * 请务必尽快返回本子程序，否则会卡住其他插件以及主程序的加载。
   *
   * @return 请固定返回0
   */
  @Override
  public int startup() {
    log.info("应用加载完毕........");
    CQ.logInfo("应用初始化", "登录QQ为 ==> " + CQ.getLoginQQ());
    String appDirectory = CQ.getAppDirectory();
    // 输出群列表
    String groupList =
        CQ.getGroupList().stream()
            .map(g -> g.getId() + "\t" + g.getName() + "\t" + "关")
            .collect(Collectors.joining("\n"));
    try {
      Files.write(Paths.get(appDirectory + "list.txt"), groupList.getBytes("UTF-8"));
    } catch (Exception e) {
      CQ.logError("配置文件", e.getMessage());
    }

    // 加载配置

    CQ.logInfo("应用初始化", "获取app文件夹==> " + appDirectory);
    File file = new File(appDirectory + "config.xlsx");
    if (file.exists()) {
      CQ.logInfo("应用初始化", "配置文件找到==> " + file.getName());
      // 初始化
      ConfigManage.init(file, CQ);
    } else {
      CQ.logError("应用初始化", "配置文件不存在==> " + appDirectory + "config.xlsx");
    }

    //    List<Group> groupList = CQ.getGroupList();
    //    groupList.forEach(
    //        group -> {
    //          CQ.logDebug("应用初始化", "获取群信息==> " + group.getName() + "[" + group.getId() + "]");
    //        });

    return 0;
  }

  /**
   * 酷Q退出 (Type={@link IType#EVENT_Exit 1002})<br>
   * 本方法会在酷Q【主线程】中被调用。<br>
   * 无论本应用是否被启用，本函数都会在酷Q退出前执行一次，请在这里执行插件关闭代码。
   *
   * @return 请固定返回0，返回后酷Q将很快关闭，请不要再通过线程等方式执行其他代码。
   */
  @Override
  public int exit() {
    log.info("程序退出，导出运行数据...");
    return 0;
  }

  /**
   * 应用已被启用 (Type={@link IType#EVENT_Enable 1003})<br>
   * 当应用被启用后，将收到此事件。<br>
   * 如果酷Q载入时应用已被启用，则在 {@link #startup startup}(Type=1001,酷Q启动) 被调用后，本函数也将被调用一次。<br>
   * 如非必要，不建议在这里加载窗口。
   *
   * @return 请固定返回0。
   */
  @Override
  public int enable() {
    log.info("应用被启用。。。。。");
    return 0;
  }

  /**
   * 应用将被停用 (Type={@link IType#EVENT_Disable 1004})<br>
   * 当应用被停用前，将收到此事件。<br>
   * 如果酷Q载入时应用已被停用，则本函数【不会】被调用。<br>
   * 无论本应用是否被启用，酷Q关闭前本函数都【不会】被调用。
   *
   * @return 请固定返回0。
   */
  @Override
  public int disable() {
    log.info("应用被停用。。。。。");
    CQ.logInfo("应用停用", "应用被停用。。。。。");
    return 0;
  }

  /**
   * 私聊消息 (Type={@link IType#EVENT_PrivateMsg 21})<br>
   * 本方法会在酷Q【线程】中被调用。<br>
   *
   * @param subType 子类型，11/来自好友 1/来自在线状态 2/来自群 3/来自讨论组
   * @param msgId 消息ID
   * @param fromQQ 来源QQ
   * @param msg 消息内容
   * @param font 字体
   * @return 返回值*不能*直接返回文本 如果要回复消息，请调用api发送<br>
   *     这里 返回 {@link IMsg#MSG_INTERCEPT} - 截断本条消息，不再继续处理<br>
   *     注意：应用优先级设置为"最高"(10000)时，不得使用本返回值<br>
   *     如果不回复消息，交由之后的应用/过滤器处理，这里 返回 {@link IMsg#MSG_IGNORE MSG_IGNORE} - 忽略本条消息
   */
  @Override
  public int privateMsg(int subType, int msgId, long fromQQ, String msg, int font) {
    if (subType == 11) {
      // 只有好友的消息才当成指令
      if (ConfigManage.isAdmin(fromQQ)) {
        // 解析命令
        Gson gson = new Gson();
        Command command = gson.fromJson(msg, Command.class);
        // 获取指令对象
        BaseCommand baseCommand = CommandFacatory.createInstance(CQ, command, fromQQ);
        // 执行
        baseCommand.run();

      } else {
        CQ.sendPrivateMsg(fromQQ, "你不是管理员，我才不听你指挥呢！就是这么拽，你来打我呀！");
      }
    }
    String fromType = null;
    switch (subType) {
      case 1:
        fromType = "在线状态";
        break;
      case 2:
        fromType = "来自群";
        break;
      case 3:
        fromType = "讨论组";
        break;
      case 11:
        fromType = "好友";
        break;
    }
    log.info(
        "[QQ] 消息来源 ==> "
            + fromType
            + " ,消息ID ==> "
            + msgId
            + " , 来源QQ ==> "
            + fromQQ
            + " , 消息内容 ==> "
            + msg
            + " , 字体 ==> "
            + font);
    CQ.logInfo(
        "QQ消息",
        "消息来源 ==> "
            + fromType
            + " ,消息ID ==> "
            + msgId
            + " , 来源QQ ==> "
            + fromQQ
            + " , 消息内容 ==> "
            + msg
            + " , 字体 ==> "
            + font);

    return 0;
  }

  /**
   * 群消息 (Type={@link IType#EVENT_GroupMsg 2})<br>
   * 本方法会在酷Q【线程】中被调用。<br>
   *
   * @param subType 子类型，目前固定为1
   * @param msgId 消息ID
   * @param fromGroup 来源群号
   * @param fromQQ 来源QQ号
   * @param fromAnonymous 来源匿名者
   * @param msg 消息内容
   * @param font 字体
   * @return 关于返回值说明, 见 {@link #privateMsg 私聊消息} 的方法
   */
  @Override
  public int groupMsg(
      int subType,
      int msgId,
      long fromGroup,
      long fromQQ,
      String fromAnonymous,
      String msg,
      int font) {

    // 禁言关键字检测
    ForbiddenWordBank forbiddenWordBank = ConfigManage.getProhibitoryDuration(fromGroup, msg);

    if (forbiddenWordBank != null) {
      if (ConfigManage.getWarnNumbers(fromGroup, fromQQ) >= forbiddenWordBank.getNumber()) {
        // 移除群成员
        CQ.setGroupKick(fromGroup, fromQQ, false);
        // 计数器归零
        ConfigManage.removeUser(fromGroup, fromQQ);
        return 0;
      }
      // 禁言
      CQ.setGroupBan(fromGroup, fromQQ, forbiddenWordBank.getLengthOfTime());
      // 记录警告
      ConfigManage.addWarn(fromGroup, fromQQ);
      // 撤回
      CQ.deleteMsg(msgId);
      // 发警告
      CQ.sendGroupMsg(fromGroup, CC.at(fromQQ) + "\n" + forbiddenWordBank.getWarn());
      CQ.logInfo(
          "群消息",
          "[禁言]群["
              + fromGroup
              + "]成员["
              + fromQQ
              + "]触发禁言 ==>"
              + forbiddenWordBank.getLengthOfTime());
    }

    // 关键词回复
    KeywordReply keywordReply = ConfigManage.getKeywordReplys(fromGroup, msg);
    // 如果获取到的不为空，并且没超过关闭的时间
    if (Objects.nonNull(keywordReply)
        && LocalDateTime.now()
            .isBefore(
                LocalDateTime.ofInstant(
                    keywordReply.getOffTime().toInstant(), ZoneId.systemDefault()))) {
      CQ.sendGroupMsg(fromGroup, CC.at(fromQQ) + "\n" + keywordReply.getReplyContent());
      return 0;
    }

    // 被@ 判断
    Set<String> propose = ConfigManage.getSuggesterByGroupId(fromGroup);

    if (StringUtils.indexOf(msg, CC.at(CQ.getLoginQQ())) >= 0 && propose != null) {
      String proposeStr =
          propose.stream()
              .filter(k -> StringUtils.indexOf(msg, k) >= 0)
              .collect(Collectors.joining("\n"));
      if (StringUtils.isNotBlank(proposeStr)) {

        CQ.sendGroupMsg(fromGroup, CC.at(fromQQ) + "\n猜你需要:\n" + proposeStr);
      } else {
        Suggester suggester = new Suggester();
        propose.iterator().forEachRemaining(k -> suggester.addSentence(k));
        String suggest = suggester.suggest(msg, 5).stream().collect(Collectors.joining("\n"));
        CQ.sendGroupMsg(fromGroup, CC.at(fromQQ) + "\n不太明白您的意思，猜你需要:\n" + suggest);
      }

      return 0;
    }

    log.info(
        "[QQ群] 消息来源 ==> "
            + subType
            + " ,消息ID ==> "
            + msgId
            + " , 来源QQ群号 ==> "
            + fromGroup
            + " , 来源QQ ==> "
            + fromQQ
            + " , 消息内容 ==> "
            + msg
            + " , 字体 ==> "
            + font);
    CQ.logInfo(
        "群消息",
        "消息来源 ==> "
            + subType
            + " ,消息ID ==> "
            + msgId
            + " , 来源QQ群号 ==> "
            + fromGroup
            + " , 来源QQ ==> "
            + fromQQ
            + " , 消息内容 ==> "
            + msg
            + " , 字体 ==> "
            + font);

    return 0;
  }

  /**
   * 讨论组消息 (Type={@link IType#EVENT_DiscussMsg 4})<br>
   * 本方法会在酷Q【线程】中被调用。<br>
   *
   * @param subtype 子类型，目前固定为1
   * @param msgId 消息ID
   * @param fromDiscuss 来源讨论组
   * @param fromQQ 来源QQ号
   * @param msg 消息内容
   * @param font 字体
   * @return 关于返回值说明, 见 {@link #privateMsg 私聊消息} 的方法
   */
  @Override
  public int discussMsg(
      int subtype, int msgId, long fromDiscuss, long fromQQ, String msg, int font) {
    log.info(
        "[讨论组]  消息来源 ==> "
            + subtype
            + " ,消息ID ==> "
            + msgId
            + " , 来源讨论组 ==> "
            + fromDiscuss
            + " , 来源QQ ==> "
            + fromQQ
            + " , 消息内容 ==> "
            + msg
            + " , 字体 ==> "
            + font);
    CQ.logInfo(
        "讨论组消息",
        "消息来源 ==> "
            + subtype
            + " ,消息ID ==> "
            + msgId
            + " , 来源讨论组 ==> "
            + fromDiscuss
            + " , 来源QQ ==> "
            + fromQQ
            + " , 消息内容 ==> "
            + msg
            + " , 字体 ==> "
            + font);

    return 0;
  }

  /**
   * 群文件上传事件 (Type={@link IType#EVENT_GroupUpload 11})<br>
   * 本方法会在酷Q【线程】中被调用。<br>
   *
   * @param subType 子类型，目前固定为1
   * @param sendTime 发送时间(时间戳)// 10位时间戳
   * @param fromGroup 来源群号
   * @param fromQQ 来源QQ号
   * @param file 上传文件信息
   * @return 关于返回值说明, 见 {@link #privateMsg 私聊消息} 的方法
   */
  @Override
  public int groupUpload(int subType, int sendTime, long fromGroup, long fromQQ, String file) {
    log.info(
        "[文件上传] ==> "
            + subType
            + " ,上传时间 ==> "
            + sendTime
            + " , 来源QQ群 ==> "
            + fromGroup
            + " , 来源QQ ==> "
            + fromQQ
            + " , 文件信息 ==> "
            + file);
    CQ.logInfo(
        "文件上传",
        "subType ==> "
            + subType
            + " ,上传时间 ==> "
            + sendTime
            + " , 来源QQ群 ==> "
            + fromGroup
            + " , 来源QQ ==> "
            + fromQQ
            + " , 文件信息 ==> "
            + file);

    return 0;
  }

  /**
   * 群事件-管理员变动 (Type={@link IType#EVENT_System_GroupAdmin 101})<br>
   * 本方法会在酷Q【线程】中被调用。<br>
   *
   * @param subtype 子类型，1/被取消管理员 2/被设置管理员
   * @param sendTime 发送时间(时间戳)
   * @param fromGroup 来源群号
   * @param beingOperateQQ 被操作QQ
   * @return 关于返回值说明, 见 {@link #privateMsg 私聊消息} 的方法
   */
  @Override
  public int groupAdmin(int subtype, int sendTime, long fromGroup, long beingOperateQQ) {
    String type = subtype == 1 ? "被取消管理员" : "被设置管理员";

    log.info(
        "[群管理员变动] 变动类型 ==> "
            + type
            + " ,发送时间 ==> "
            + sendTime
            + " , 来源群号 ==> "
            + fromGroup
            + " , 被操作QQ ==> "
            + beingOperateQQ);
    CQ.logInfo(
        "群管理员变动",
        "变动类型 ==> "
            + type
            + " ,发送时间 ==> "
            + sendTime
            + " , 来源群号 ==> "
            + fromGroup
            + " , 被操作QQ ==> "
            + beingOperateQQ);

    return 0;
  }

  /**
   * 群事件-群成员减少 (Type={@link IType#EVENT_System_GroupMemberDecrease 102})<br>
   * 本方法会在酷Q【线程】中被调用。<br>
   *
   * @param subtype 子类型，1/群员离开 2/群员被踢
   * @param sendTime 发送时间(时间戳)
   * @param fromGroup 来源群号
   * @param fromQQ 操作者QQ(仅子类型为2时存在)
   * @param beingOperateQQ 被操作QQ
   * @return 关于返回值说明, 见 {@link #privateMsg 私聊消息} 的方法
   */
  @Override
  public int groupMemberDecrease(
      int subtype, int sendTime, long fromGroup, long fromQQ, long beingOperateQQ) {
    String type = subtype == 1 ? "主动退群" : "被踢";
    log.info(
        "[退群] 变动类型 ==> "
            + type
            + " ,发送时间 ==> "
            + sendTime
            + " , 来源群号 ==> "
            + fromGroup
            + " , 管理员 ==>"
            + fromQQ
            + ", 被操作QQ ==> "
            + beingOperateQQ);
    CQ.logInfo(
        "退群",
        "变动类型 ==> "
            + type
            + " ,发送时间 ==> "
            + sendTime
            + " , 来源群号 ==> "
            + fromGroup
            + " , 管理员 ==>"
            + fromQQ
            + ", 被操作QQ ==> "
            + beingOperateQQ);

    return 0;
  }

  /**
   * 群事件-群成员增加 (Type={@link IType#EVENT_System_GroupMemberIncrease 103})<br>
   * 本方法会在酷Q【线程】中被调用。<br>
   *
   * @param subtype 子类型，1/管理员已同意 2/管理员邀请
   * @param sendTime 发送时间(时间戳)
   * @param fromGroup 来源群号
   * @param fromQQ 操作者QQ(即管理员QQ)
   * @param beingOperateQQ 被操作QQ(即加群的QQ)
   * @return 关于返回值说明, 见 {@link #privateMsg 私聊消息} 的方法
   */
  @Override
  public int groupMemberIncrease(
      int subtype, int sendTime, long fromGroup, long fromQQ, long beingOperateQQ) {
    String type = subtype == 1 ? "管理员已同意" : "管理员邀请";

    log.info(
        "[群成员增加] 变动类型 ==> "
            + type
            + " ,发送时间 ==> "
            + sendTime
            + " , 来源群号 ==> "
            + fromGroup
            + " , 管理员 ==>"
            + fromQQ
            + ", 加群的QQ ==> "
            + beingOperateQQ);
    CQ.logInfo(
        "群成员增加",
        "变动类型 ==> "
            + type
            + " ,发送时间 ==> "
            + sendTime
            + " , 来源群号 ==> "
            + fromGroup
            + " , 管理员 ==>"
            + fromQQ
            + ", 加群的QQ ==> "
            + beingOperateQQ);

    return 0;
  }

  /**
   * 好友事件-好友已添加 (Type={@link IType#EVENT_Friend_Add 201})<br>
   * 本方法会在酷Q【线程】中被调用。<br>
   *
   * @param subtype 子类型，目前固定为1
   * @param sendTime 发送时间(时间戳)
   * @param fromQQ 来源QQ
   * @return 关于返回值说明, 见 {@link #privateMsg 私聊消息} 的方法
   */
  @Override
  public int friendAdd(int subtype, int sendTime, long fromQQ) {
    log.info("[好友已添加] 变动类型 ==> " + subtype + " ,发送时间 ==> " + sendTime + " , 来源QQ ==> " + fromQQ);
    CQ.logInfo("好友已添加", "变动类型 ==> " + subtype + " ,发送时间 ==> " + sendTime + " , 来源QQ ==> " + fromQQ);
    return 0;
  }

  /**
   * 请求-好友添加 (Type={@link IType#EVENT_Request_AddFriend 301})<br>
   * 本方法会在酷Q【线程】中被调用。<br>
   *
   * @param subtype 子类型，目前固定为1
   * @param sendTime 发送时间(时间戳)
   * @param fromQQ 来源QQ
   * @param msg 附言
   * @param responseFlag 反馈标识(处理请求用)
   * @return 关于返回值说明, 见 {@link #privateMsg 私聊消息} 的方法
   */
  @Override
  public int requestAddFriend(
      int subtype, int sendTime, long fromQQ, String msg, String responseFlag) {
    log.info(
        "[好友添加请求] 变动类型 ==> "
            + subtype
            + " ,发送时间 ==> "
            + sendTime
            + " , 来源QQ ==> "
            + fromQQ
            + " , 附言 ==> "
            + msg
            + ", 反馈标识 ==> "
            + responseFlag);

    CQ.logInfo(
        "好友添加请求",
        " 变动类型 ==> "
            + subtype
            + " ,发送时间 ==> "
            + sendTime
            + " , 来源QQ ==> "
            + fromQQ
            + " , 附言 ==> "
            + msg
            + ", 反馈标识 ==> "
            + responseFlag);

    return 0;
  }

  /**
   * 请求-群添加 (Type={@link IType#EVENT_Request_AddGroup 302})<br>
   * 本方法会在酷Q【线程】中被调用。<br>
   *
   * @param subtype 子类型，1/他人申请入群 2/自己(即登录号)受邀入群
   * @param sendTime 发送时间(时间戳)
   * @param fromGroup 来源群号
   * @param fromQQ 来源QQ
   * @param msg 附言
   * @param responseFlag 反馈标识(处理请求用)
   * @return 关于返回值说明, 见 {@link #privateMsg 私聊消息} 的方法
   */
  @Override
  public int requestAddGroup(
      int subtype, int sendTime, long fromGroup, long fromQQ, String msg, String responseFlag) {
    String type = subtype == 1 ? "申请入群" : "被邀请入群";
    // 判断如果在黑名单中则不允许入群
    if (subtype == 1 && ConfigManage.existBlackList(fromGroup, fromQQ)) {
      // 如果是他人申请入群
      CQ.setGroupAddRequest(responseFlag, IRequest.REQUEST_GROUP_ADD, IRequest.REQUEST_REFUSE, "");
      CQ.logInfo("群添加", "[拒绝]黑名单[" + fromQQ + "]添加群[" + fromGroup + "]失败 !");
    } else {
      // 同意
      CQ.setGroupAddRequest(responseFlag, IRequest.REQUEST_GROUP_ADD, IRequest.REQUEST_ADOPT, "");
      CQ.logInfo(
          "群添加",
          "[同意]变动类型 ==> "
              + type
              + " ,发送时间 ==> "
              + sendTime
              + " , 来源群号==> "
              + fromGroup
              + " , 来源QQ ==> "
              + fromQQ
              + " , 附言 ==> "
              + msg
              + ", 反馈标识 ==> "
              + responseFlag);
      // 禁言判断
      Integer forbiddenSpeechLength = ConfigManage.getForbiddenSpeechLength(fromGroup);
      if (forbiddenSpeechLength != null) {
        CQ.setGroupBan(fromGroup, fromQQ, forbiddenSpeechLength);
        CQ.logInfo(
            "群添加",
            "来源群号==> "
                + fromGroup
                + " , 来源QQ ==> "
                + fromQQ
                + " , 附言 ==> "
                + msg
                + " , 禁言时长 ==> "
                + forbiddenSpeechLength);
      }

      // 欢迎判断
      String greet = ConfigManage.getGreet(fromGroup);
      if (Objects.nonNull(greet)) {
        CQ.sendGroupMsg(fromGroup, CC.at(fromQQ) + greet);
        CQ.logInfo(
            "群添加",
            "来源群号==> "
                + fromGroup
                + " , 来源QQ ==> "
                + fromQQ
                + " , 附言 ==> "
                + msg
                + " , 欢迎 ==> "
                + greet);
      }

      // 私聊判断
      String privateChat = ConfigManage.getPrivateChat(fromGroup);
      if (Objects.nonNull(privateChat)) {
        CQ.sendPrivateMsg(fromQQ, privateChat);
        CQ.logInfo(
            "群添加",
            "来源群号==> "
                + fromGroup
                + " , 来源QQ ==> "
                + fromQQ
                + " , 附言 ==> "
                + msg
                + " , 私聊 ==> "
                + privateChat);
      }
    }

    log.info(
        "[群添加] 变动类型 ==> "
            + type
            + " ,发送时间 ==> "
            + sendTime
            + " , 来源群号==> "
            + fromGroup
            + " , 来源QQ ==> "
            + fromQQ
            + " , 附言 ==> "
            + msg
            + ", 反馈标识 ==> "
            + responseFlag);

    return 0;
  }
  /**
   * 本函数会在JCQ【线程】中被调用。
   *
   * @return 固定返回0
   */
  public int menuA() {
    JOptionPane.showMessageDialog(null, "这是测试菜单A，可以在这里加载窗口");
    return 0;
  }

  /**
   * 本函数会在酷Q【线程】中被调用。
   *
   * @return 固定返回0
   */
  public int menuB() {
    JOptionPane.showMessageDialog(null, "这是测试菜单B，可以在这里加载窗口");
    return 0;
  }
}
